# 神经网络编程的案例：识别手写数字

import numpy as np
import matplotlib.pyplot as plt
import scipy.special

# 神经网络
class neuralNetwork:
    def __init__(self, inputnodes, hiddennodes, ouputnodes, learningrate):
        self.inodes = inputnodes
        self.hnodes = hiddennodes
        self.onodes = ouputnodes

        #权重矩阵,表示输入层到隐藏层之间的权重,矩阵的形状是 (self.hnodes, self.onodes)
        self.wih = np.random.normal(0.0, pow(self.hnodes, -0.5), (self.hnodes, self.inodes))
        
        #权重矩阵,表示隐藏层到输出层之间的权重,矩阵的形状是 (self.onodes, self.hnodes)
        self.who = np.random.normal(0.0, pow(self.onodes, -0.5), (self.onodes, self.hnodes))

        #学习速率
        self.lr = learningrate

        #激活函数
        self.activation_function = lambda x : scipy.special.expit(x)
        pass

    # 训练
    # 实现了神经网络的前向传播和反向传播过程；
    # 通过计算输出误差并利用梯度下降法更新网络的权重，使得网络能够逐步学习输入数据与目标数据之间的映射关系
    def train(self, inputs_list, target_list):
        # print("self.wih.shape:", self.wih.shape)
        # print("self.who.shape:", self.who.shape)

        #1.输入处理：将输入和目标列表转为numpy数组，并确保至少有2维，然后转置，使得数据是列向量的形式，可以做矩阵运算
        inputs = np.array(inputs_list, ndmin=2).T
        targets = np.array(target_list, ndmin=2).T
        # print("inputs.shap:", inputs.shape)
        # print("targets.shape:", targets.shape)

        #2.前向传播
        #隐藏层输入和输出
        hidden_inputs = np.dot(self.wih, inputs)
        hidden_outputs = self.activation_function(hidden_inputs)

        #输出层输入和输出
        final_inputs = np.dot(self.who, hidden_outputs)
        final_outputs = self.activation_function(final_inputs)
        
        #3.计算误差：输出层的误差
        output_errors = targets - final_outputs

        #4.反向传播
        #隐藏层的误差
        hidden_errors = np.dot(self.who.T, output_errors)
        #更新隐藏层到输出层的权重
        self.who += self.lr * np.dot((output_errors * final_outputs * (1.0 - final_outputs)), np.transpose(hidden_outputs))
        #更新输入层到隐藏层的权重
        self.wih += self.lr * np.dot((hidden_errors * hidden_outputs * (1.0 - hidden_outputs)), np.transpose(inputs))
        pass

    # 使用已经训练好的神经网络模型对输入数据进行推理，并返回预测结果
    def query(self, inputs_lists):
        #1.输入处理：将输入列表转为numpy数组，并确保至少有2维，然后转置，使得数据是列向量的形式，可以做矩阵运算
        inputs = np.array(inputs_lists, ndmin=2).T

        #2.前向传播
        hidden_inputs = np.dot(self.wih, inputs)
        hidden_outputs = self.activation_function(hidden_inputs)

        final_inputs = np.dot(self.who, hidden_outputs)
        final_outputs = self.activation_function(final_inputs)
        
        return final_outputs


# 输入节点
input_nodes = 784  
# 隐藏节点
hidden_nodes = 100
# 输出节点
ouput_nodes = 10
# 学习率
learning_rate = 0.1

n = neuralNetwork(input_nodes, hidden_nodes, ouput_nodes, learning_rate)
# n.query([1.0, 0.5, -1.5])

# print("open")
training_data_file = open("mnist_train_60000.csv", 'r')
training_data_list = training_data_file.readlines()
training_data_file.close()

#训练的总轮数
epochs = 3
for e in range(epochs):
    for record in training_data_list:
        all_values = record.split(',')
        input_values = np.array(all_values[1:], dtype=np.float64)
        inputs = (input_values / 255.0 * 0.99) + 0.01
        targets = np.zeros(ouput_nodes) + 0.01
        target_index = int(all_values[0])
        targets[target_index] = 0.99
        n.train(inputs, targets)
        pass
    pass

# print("test network")
test_data_file = open("mnist_test_10000.csv", 'r')
test_data_list = test_data_file.readlines()
test_data_file.close()

# all_values = test_data_list[0].split(',')
# print(all_values[0])

# image_array = np.array(all_values[1:], dtype=np.float64).reshape((28,28))
# plt.imshow(image_array, cmap='Greys', interpolation='None')
# plt.colorbar()
# plt.title("Cray Scale Image")
# plt.show()

# input_values = np.array(all_values[1:], dtype=np.float64)
# inputs = (input_values / 255.0 * 0.99) + 0.01
# ouputs = n.query(inputs)
# print(ouputs)

# 跑测试集，计算得分
# print("calc score")
score_card = []
for record in test_data_list:
    all_values = record.split(',')
    correct_label = int(all_values[0])
    # print(correct_label, "correct label")

    input_values = np.array(all_values[1:], dtype=np.float64)
    inputs = (input_values / 255.0 * 0.99) + 0.01
    outputs = n.query(inputs)
    label = np.argmax(outputs)

    # print(label, "network's answer")
    if(label == correct_label):
        score_card.append(1)
    else:
        score_card.append(0)
        pass
    pass

score_card_array = np.asarray(score_card)
print("lr:",learning_rate, "epochs:",epochs, "peformance=", 
      score_card_array.sum() / score_card_array.size)
